package cn.snmp.mutual.uk.stack;

import cn.snmp.mutual.uk.event.RawPduListener;
import cn.snmp.mutual.uk.event.RawPduReceivedSupport;
import cn.snmp.mutual.uk.net.ContextSocketFace;
import cn.snmp.mutual.uk.net.StreamPortItem;
import cn.snmp.mutual.uk.util.SnmpUtilities;

import java.io.*;

public class ListeningContext implements ListeningContextFace, Runnable {
    private static final String version_id = "@(#)$Id: ListeningContext.java,v 3.12 2009/03/05 13:24:00 birgita Exp $ Copyright Westhawk Ltd";
    private Object soc_lock;
    private static int counter;
    private ContextSocketFace soc;
    private Thread me;
    private String basename;
    private volatile boolean stopRequested;
    protected int maxRecvSize;
    protected String typeSocket;
    protected int hostPort;
    protected String bindAddr;
    private transient RawPduReceivedSupport pduSupport;
    private transient RawPduReceivedSupport unhandledSupport;

    public ListeningContext(final int port) {
        this(port, null, "Standard");
    }

    public ListeningContext(final int port, final String bindAddress) {
        this(port, bindAddress, "Standard");
    }

    public ListeningContext(final int port, final String bindAddress, final String typeSocketA) {
        this.soc_lock = new Object();
        this.hostPort = port;
        this.bindAddr = bindAddress;
        this.typeSocket = typeSocketA;
        this.basename = "" + this.hostPort + "_" + this.bindAddr;
        this.pduSupport = new RawPduReceivedSupport(this);
        this.unhandledSupport = new RawPduReceivedSupport(this);
        this.maxRecvSize = 1472;
    }

    public int getPort() {
        return this.hostPort;
    }

    public String getBindAddress() {
        return this.bindAddr;
    }

    public String getTypeSocket() {
        return this.typeSocket;
    }

    public int getMaxRecvSize() {
        return this.maxRecvSize;
    }

    public void setMaxRecvSize(final int no) {
        this.maxRecvSize = no;
    }

    public void destroy() {
        synchronized (this.soc_lock) {
            this.stopRequested = true;
            if (this.soc != null) {
                if (AsnObject.debug > 12) {
                    System.out.println(this.getClass().getName() + ".destroy(): Closing socket ");
                }
                this.soc.close();
            }
        }
    }

    public void run() {
        while (!this.stopRequested) {
            final Thread me = this.me;
            Thread.yield();
            try {
                if (this.stopRequested) {
                    break;
                }
                final StreamPortItem item = this.soc.receive(this.maxRecvSize);
                final ByteArrayInputStream in = item.getStream();
                final String hostAddress = item.getHostAddress();
                final int port = item.getHostPort();
                final int nb = in.available();
                final byte[] bu = new byte[nb];
                in.read(bu);
                in.close();
                if (AsnObject.debug > 10) {
                    SnmpUtilities.dumpBytes(this.getClass().getName() + ".run(): Received from " + hostAddress + ", from port " + port + ": ", bu);
                }
                final KickProcessIncomingMessage thread = new KickProcessIncomingMessage(hostAddress, port, bu);
                thread.start();
            } catch (IOException exc) {
                if (exc instanceof InterruptedIOException) {
                    if (AsnObject.debug <= 15) {
                        continue;
                    }
                    System.out.println(this.getClass().getName() + ".run(): Idle recv " + exc.getMessage());
                } else {
                    if (AsnObject.debug <= 0) {
                        continue;
                    }
                    System.out.println(this.getClass().getName() + ".run(): IOException: " + exc.getMessage());
                }
            } catch (Exception exc2) {
                if (AsnObject.debug <= 0) {
                    continue;
                }
                System.out.println(this.getClass().getName() + ".run(): Exception: " + exc2.getMessage());
                exc2.printStackTrace();
            } catch (Error err) {
                if (AsnObject.debug <= 0) {
                    continue;
                }
                System.out.println(this.getClass().getName() + ".run(): Error: " + err.getMessage());
                err.printStackTrace();
            }
        }
        this.me = null;
        this.soc = null;
        this.pduSupport.empty();
        this.unhandledSupport.empty();
    }

    public void addRawPduListener(final RawPduListener listener) throws IOException {
        synchronized (this.soc_lock) {
            this.pduSupport.addRawPduListener(listener);
            this.startListening();
        }
    }

    public void removeRawPduListener(final RawPduListener listener) {
        synchronized (this.soc_lock) {
            this.pduSupport.removeRawPduListener(listener);
            this.destroyIfNoListeners();
        }
    }

    public void addUnhandledRawPduListener(final RawPduListener listener) throws IOException {
        synchronized (this.soc_lock) {
            this.unhandledSupport.addRawPduListener(listener);
            this.startListening();
        }
    }

    public void removeUnhandledRawPduListener(final RawPduListener listener) {
        synchronized (this.soc_lock) {
            this.unhandledSupport.removeRawPduListener(listener);
            this.destroyIfNoListeners();
        }
    }

    private void startListening() throws IOException {
    }

    public String getHashKey() {
        final String str = this.hostPort + "_" + this.bindAddr + "_" + this.typeSocket;
        return str;
    }

    @Override
    public String toString() {
        final StringBuffer buffer = new StringBuffer("ListeningContext[");
        buffer.append("port=").append(this.hostPort);
        buffer.append(", bindAddress=").append(this.bindAddr);
        buffer.append(", socketType=").append(this.typeSocket);
        buffer.append(", #rawPduListeners=").append(this.pduSupport.getListenerCount());
        buffer.append(", #rawPduUnhandledListeners=").append(this.unhandledSupport.getListenerCount());
        buffer.append("]");
        return buffer.toString();
    }

    public void processIncomingMessage(final String hostAddress, final int port, final byte[] bu) throws DecodingException, IOException {
        final AsnDecoderBase rpdu = new AsnDecoderBase();
        final ByteArrayInputStream in = new ByteArrayInputStream(bu);
        final AsnSequence asnTopSeq = rpdu.getAsnSequence(in);
        final int version = rpdu.getSNMPVersion(asnTopSeq);
        final boolean isConsumed = this.pduSupport.fireRawPduReceived(version, hostAddress, port, bu);
        if (!isConsumed) {
            this.unhandledSupport.fireRawPduReceived(version, hostAddress, port, bu);
        }
    }

    private void destroyIfNoListeners() {
        if (this.pduSupport.getListenerCount() == 0 && this.unhandledSupport.getListenerCount() == 0) {
            this.destroy();
        }
    }

    class KickProcessIncomingMessage extends Thread {
        private String hostAddress;
        private int port;
        private byte[] bu;

        KickProcessIncomingMessage(final String newHostAddress, final int newPort, final byte[] newBu) {
            this.hostAddress = newHostAddress;
            this.port = newPort;
            this.bu = newBu;
            this.setPriority(1);
            this.setName(newHostAddress + "_" + newPort + "_KickProcessIncomingMessage_" + ListeningContext.counter);
            ListeningContext.counter++;
        }

        @Override
        public void run() {
            try {
                ListeningContext.this.processIncomingMessage(this.hostAddress, this.port, this.bu);
            } catch (IOException exc) {
                if (AsnObject.debug > 0) {
                    System.out.println(this.getClass().getName() + ".run(): IOException: " + exc.getMessage());
                }
            } catch (DecodingException exc2) {
                if (AsnObject.debug > 1) {
                    System.out.println(this.getClass().getName() + ".run(): DecodingException: " + exc2.getMessage());
                }
            }
        }
    }
}
